package drds.configuration.mvc.upload;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.FileRenamePolicy;
import drds.configuration.common.Constants;
import drds.configuration.mvc.config.MvcConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * Cos文件上传 经测试本框架不支持SmartUpload上传
 */
@SuppressWarnings({"unchecked"})
public class CosUpload extends HttpServletRequestWrapper {

    public static final String UploadTempFolderPath = MvcConstants.uploadTempFolderPath;
    /**
     * 注意COS组件最多上传Integer.MAX_VALUE字节
     */
    public static int MaxPostSize = Integer.MAX_VALUE;
    static Logger logging = LoggerFactory.getLogger(CosUpload.class);
    // 下面这些类型均对tomcat和windows系统有危害
    static String[] notAllowFileTypes = {".jsp", ".jspx", ".exe", ".bat", ".cgi", ".dll", ".com",
            ".pif", ".cmd"};
    // 默认是UTF-8格式,上传的jsp页面必须保证字符集为UTF-8格式
    private String characterEncoding = "utf-8";
    // 文件上传允许上传的类型
    // allowTypes = null;不等于new String{null}
    private String[] allowTypes = null;
    // com.oreilly.servlet.multipart.DefaultFileRenamePolicy;
    private FileRenamePolicy FILE_RENAME_POLICY = new SimpleCosFileRenamePolicy();
    private MultipartRequest multipartRequest;
    private List<UploadFile> uploadFiles = new ArrayList<UploadFile>();
    private String[] fields = null;
    private Map<String, String> fieldMap = null;

    /**
     * 只需关注上传的文件类型
     *
     * @param characterEncoding 默认为utf-8
     * @param allowTypes        允许上传的类型允许为空-即除了允许系统允许的可执行文件{".jsp", ".jspx", ".exe", ".bat", ".cgi", ".dll",
     *                          ".com", ".pif", ".cmd"}以外都可以
     * @param fields            支持user.username的字段
     */
    public CosUpload(HttpServletRequest request, String characterEncoding, String[] allowTypes,
                     String[] fields) {

        super(request);
        try {
            Class.forName("com.oreilly.servlet.MultipartRequest");
        } catch (ClassNotFoundException e) {
            // 每次上传确定Cos组件所需的jar包在lib或者classpath目录下
            throw new RuntimeException("缺少Cos组件所需的jar包");
        }
        if (characterEncoding != null && !"".equals(characterEncoding.trim())) {
            this.characterEncoding = characterEncoding;
        }
        if (allowTypes != null && allowTypes.length > 0 && allowTypes[0] != null) {
            this.allowTypes = allowTypes;
        }
        if (fields != null && fields.length > 0) {
            this.fields = fields;
        }
        wrapMultipartRequest(request, UploadTempFolderPath, MaxPostSize, this.characterEncoding);

    }

    /**
     * 返回所有的参数信息
     */
    public Map<String, String[]> getFieldsMap() {

        Map map = new HashMap();
        Enumeration enumm = multipartRequest.getParameterNames();
        while (enumm.hasMoreElements()) {
            String name = (String) enumm.nextElement();
            map.put(name, multipartRequest.getParameterValues(name));
        }
        return map;
    }

    /**
     * 如果上传文件不在本地保存,则删除
     */
    public void deleteFiles() {
        if (uploadFiles != null && uploadFiles.size() > 0) {
            for (int i = 0; i < uploadFiles.size(); i++) {
                try {
                    uploadFiles.get(i).getFile().delete();
                } catch (Exception e) {
                    logging.error(e.getMessage());
                }
            }
        }
    }

    /**
     * 获取表单字段信息
     */
    public Map<String, String> getFieldMap() {
        return this.fieldMap;
    }

    /**
     * 从这里可以直接操作上传的合法文件
     */
    public List<UploadFile> getFiles() {
        return uploadFiles;
    }

    @Override
    public String getParameter(String name) {
        return multipartRequest.getParameter(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map map = new HashMap();
        Enumeration enumm = getParameterNames();
        while (enumm.hasMoreElements()) {
            String name = (String) enumm.nextElement();
            map.put(name, multipartRequest.getParameterValues(name));
        }
        return map;
    }

    @Override
    public Enumeration getParameterNames() {
        return multipartRequest.getParameterNames();
    }

    @Override
    public String[] getParameterValues(String name) {
        return multipartRequest.getParameterValues(name);
    }

    private boolean isSafeFile(UploadFile uploadFile) {
        // 文件为空
        if (uploadFile == null) {
            return false;
        }
        // 绝对禁止上传的类型
        String suffix = uploadFile.getFileName().toLowerCase();
        int notAllowFileTypesCount = notAllowFileTypes.length;
        for (int i = 0; i < notAllowFileTypesCount; i++) {
            if (suffix.endsWith(notAllowFileTypes[i])) {
                uploadFile.getFile().delete();
                return false;
            }
        }
        // 只允许上传的文件类型
        // 如果allowTypesCount小于0则可以上传任意类型的文件.大于0则需要找到匹配的文件
        if (allowTypes != null && allowTypes.length > 0) {
            int allowTypesCount = allowTypes.length;
            for (int i = 0; i < allowTypesCount; i++) {
                if (suffix.endsWith(allowTypes[i])) {
                    return true;
                }
            }
            uploadFile.getFile().delete();
            return false;
        } else {
            return true;
        }

    }

    /**
     * 处理文件上传请求
     *
     * @param saveDirectory 在系统部署的时候我们已经建立这个目录-系统上传文件临时文件夹.这样保证临时文件中含有危险文件则自动删除
     */
    private void wrapMultipartRequest(HttpServletRequest request, String saveDirectory,
                                      int maxPostSize, String characterEncoding) {
        File dir = new File(saveDirectory);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new RuntimeException("上传的文件夹不存在且不能创建");
            }
        }
        try {
            this.multipartRequest = new MultipartRequest(request, saveDirectory, maxPostSize,
                    characterEncoding, this.FILE_RENAME_POLICY);
            if (this.multipartRequest == null) {
                return;
            }
            /**
             * 得到所有已上传的文件名
             */

            Enumeration fileNames = this.multipartRequest.getFileNames();
            // 文件处理
            /**
             * 注意文件上传的时候每个文件域必须要有唯一的name,否则只能上传一个文件
             */
            while (fileNames.hasMoreElements()) {
                String multipartRequestFileNames = (String) fileNames.nextElement();
                /**
                 * 得到该文件的现上传后的文件名
                 */
                String filesystemName = this.multipartRequest.getFilesystemName(multipartRequestFileNames);
                // 文件没有上传则不生成 UploadFile--如果不存在这个文件文件名自然不会生产
                if (filesystemName != null) {
                    /**
                     * 得到该文件上传前的文件名(即原始名)
                     */
                    String originalFileName = this.multipartRequest
                            .getOriginalFileName(multipartRequestFileNames);
                    // 文件类型
                    String contentType = this.multipartRequest.getContentType(multipartRequestFileNames);
                    UploadFile uploadFile = new UploadFile(multipartRequestFileNames, saveDirectory,
                            filesystemName, originalFileName, contentType);
                    try {
                        if (isSafeFile(uploadFile)) {
                            if (Constants.devMode) {
                                logging.info("文件地址:" + uploadFile.getFilePath());
                            }
                            uploadFiles.add(uploadFile);
                        }
                    } catch (Exception e) {
                        // 可能不成功-则需要将上传的临时文件夹里面的文件删除
                    }
                }
            }
            /**
             * 表单参数处理
             */
            if (fields != null && fields.length > 0) {
                fieldMap = new HashMap<String, String>();
                int length = fields.length;
                if (length > 0) {
                    for (int i = 0; i < length; i++) {
                        try {
                            String key = fields[i].trim();
                            if (key != null && !"".equals(key.trim())) {
                                String value = this.multipartRequest.getParameter(key);
                                fieldMap.put(key, value);
                            }

                        } catch (Exception e) {
                            // 出现异常则不加入到fieldMap里面
                        }
                    }
                }

            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}
